home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 4 / Apprentice-Release4.iso / Source Code / C / Applications / Python 1.3 / source code / Lib / urllib.py < prev    next >
Encoding:
Python Source  |  1995-12-17  |  17.8 KB  |  653 lines  |  [TEXT/R*ch]

  1. # Open an arbitrary URL
  2. #
  3. # See the following document for a tentative description of URLs:
  4. #     Uniform Resource Locators              Tim Berners-Lee
  5. #     INTERNET DRAFT                                    CERN
  6. #     IETF URL Working Group                    14 July 1993
  7. #     draft-ietf-uri-url-01.txt
  8. #
  9. # The object returned by URLopener().open(file) will differ per
  10. # protocol.  All you know is that is has methods read(), readline(),
  11. # readlines(), fileno(), close() and info().  The read*(), fileno()
  12. # and close() methods work like those of open files. 
  13. # The info() method returns an mimetools.Message object which can be
  14. # used to query various info about the object, if available.
  15. # (mimetools.Message objects are queried with the getheader() method.)
  16.  
  17. import string
  18. import socket
  19. import regex
  20.  
  21.  
  22. __version__ = '1.2'
  23.  
  24.  
  25. # This really consists of two pieces:
  26. # (1) a class which handles opening of all sorts of URLs
  27. #     (plus assorted utilities etc.)
  28. # (2) a set of functions for parsing URLs
  29. # XXX Should these be separated out into different modules?
  30.  
  31.  
  32. # Shortcut for basic usage
  33. _urlopener = None
  34. def urlopen(url):
  35.     global _urlopener
  36.     if not _urlopener:
  37.         _urlopener = FancyURLopener()
  38.     return _urlopener.open(url)
  39. def urlretrieve(url):
  40.     global _urlopener
  41.     if not _urlopener:
  42.         _urlopener = FancyURLopener()
  43.     return _urlopener.retrieve(url)
  44. def urlcleanup():
  45.     if _urlopener:
  46.         _urlopener.cleanup()
  47.  
  48.  
  49. # Class to open URLs.
  50. # This is a class rather than just a subroutine because we may need
  51. # more than one set of global protocol-specific options.
  52. # Note -- this is a base class for those who don't want the
  53. # automatic handling of errors type 302 (relocated) and 401
  54. # (authorization needed).
  55. ftpcache = {}
  56. class URLopener:
  57.  
  58.     # Constructor
  59.     def __init__(self):
  60.         server_version = "Python-urllib/%s" % __version__
  61.         self.addheaders = [('User-agent', server_version)]
  62.         self.tempcache = None
  63.         # Undocumented feature: if you assign {} to tempcache,
  64.         # it is used to cache files retrieved with
  65.         # self.retrieve().  This is not enabled by default
  66.         # since it does not work for changing documents (and I
  67.         # haven't got the logic to check expiration headers
  68.         # yet).
  69.         self.ftpcache = ftpcache
  70.         # Undocumented feature: you can use a different
  71.         # ftp cache by assigning to the .ftpcache member;
  72.         # in case you want logically independent URL openers
  73.  
  74.     def __del__(self):
  75.         self.close()
  76.  
  77.     def close(self):
  78.         self.cleanup()
  79.  
  80.     def cleanup(self):
  81.         import os
  82.         if self.tempcache:
  83.             for url in self.tempcache.keys():
  84.                 try:
  85.                     os.unlink(self.tempcache[url][0])
  86.                 except os.error:
  87.                     pass
  88.                 del self.tempcache[url]
  89.  
  90.     # Add a header to be used by the HTTP interface only
  91.     # e.g. u.addheader('Accept', 'sound/basic')
  92.     def addheader(self, *args):
  93.         self.addheaders.append(args)
  94.  
  95.     # External interface
  96.     # Use URLopener().open(file) instead of open(file, 'r')
  97.     def open(self, fullurl):
  98.         fullurl = unwrap(fullurl)
  99.         type, url = splittype(fullurl)
  100.          if not type: type = 'file'
  101.         name = 'open_' + type
  102.         if '-' in name:
  103.             import regsub
  104.             name = regsub.gsub('-', '_', name)
  105.         if not hasattr(self, name):
  106.             return self.open_unknown(fullurl)
  107.         try:
  108.             return getattr(self, name)(url)
  109.         except socket.error, msg:
  110.             raise IOError, ('socket error', msg)
  111.  
  112.     # Overridable interface to open unknown URL type
  113.     def open_unknown(self, fullurl):
  114.         type, url = splittype(fullurl)
  115.         raise IOError, ('url error', 'unknown url type', type)
  116.  
  117.     # External interface
  118.     # retrieve(url) returns (filename, None) for a local object
  119.     # or (tempfilename, headers) for a remote object
  120.     def retrieve(self, url):
  121.         if self.tempcache and self.tempcache.has_key(url):
  122.             return self.tempcache[url]
  123.         url1 = unwrap(url)
  124.         if self.tempcache and self.tempcache.has_key(url1):
  125.             self.tempcache[url] = self.tempcache[url1]
  126.             return self.tempcache[url1]
  127.         type, url1 = splittype(url1)
  128.         if not type or type == 'file':
  129.             try:
  130.                 fp = self.open_local_file(url1)
  131.                 del fp
  132.                 return splithost(url1)[1], None
  133.             except IOError, msg:
  134.                 pass
  135.         fp = self.open(url)
  136.         headers = fp.info()
  137.         import tempfile
  138.         tfn = tempfile.mktemp()
  139.         result = tfn, headers
  140.         if self.tempcache is not None:
  141.             self.tempcache[url] = result
  142.         tfp = open(tfn, 'w')
  143.         bs = 1024*8
  144.         block = fp.read(bs)
  145.         while block:
  146.             tfp.write(block)
  147.             block = fp.read(bs)
  148.         del fp
  149.         del tfp
  150.         return result
  151.  
  152.     # Each method named open_<type> knows how to open that type of URL
  153.  
  154.     # Use HTTP protocol
  155.     def open_http(self, url):
  156.         import httplib
  157.         host, selector = splithost(url)
  158.         if not host: raise IOError, ('http error', 'no host given')
  159.         i = string.find(host, '@')
  160.         if i >= 0:
  161.             user_passwd, host = host[:i], host[i+1:]
  162.         else:
  163.             user_passwd = None
  164.         if user_passwd:
  165.             import base64
  166.             auth = string.strip(base64.encodestring(user_passwd))
  167.         else:
  168.             auth = None
  169.         h = httplib.HTTP(host)
  170.         h.putrequest('GET', selector)
  171.         if auth: h.putheader('Authorization: Basic %s' % auth)
  172.         for args in self.addheaders: apply(h.putheader, args)
  173.         h.endheaders()
  174.         errcode, errmsg, headers = h.getreply()
  175.         fp = h.getfile()
  176.         if errcode == 200:
  177.             return addinfo(fp, headers)
  178.         else:
  179.             return self.http_error(url,
  180.                            fp, errcode, errmsg, headers)
  181.  
  182.     # Handle http errors.
  183.     # Derived class can override this, or provide specific handlers
  184.     # named http_error_DDD where DDD is the 3-digit error code
  185.     def http_error(self, url, fp, errcode, errmsg, headers):
  186.         # First check if there's a specific handler for this error
  187.         name = 'http_error_%d' % errcode
  188.         if hasattr(self, name):
  189.             method = getattr(self, name)
  190.             result = method(url, fp, errcode, errmsg, headers)
  191.             if result: return result
  192.         return self.http_error_default(
  193.             url, fp, errcode, errmsg, headers)
  194.  
  195.     # Default http error handler: close the connection and raises IOError
  196.     def http_error_default(self, url, fp, errcode, errmsg, headers):
  197.         void = fp.read()
  198.         fp.close()
  199.         raise IOError, ('http error', errcode, errmsg, headers)
  200.  
  201.     # Use Gopher protocol
  202.     def open_gopher(self, url):
  203.         import gopherlib
  204.         host, selector = splithost(url)
  205.         if not host: raise IOError, ('gopher error', 'no host given')
  206.         type, selector = splitgophertype(selector)
  207.         selector, query = splitquery(selector)
  208.         selector = unquote(selector)
  209.         if query:
  210.             query = unquote(query)
  211.             fp = gopherlib.send_query(selector, query, host)
  212.         else:
  213.             fp = gopherlib.send_selector(selector, host)
  214.         return addinfo(fp, noheaders())
  215.  
  216.     # Use local file or FTP depending on form of URL
  217.     def open_file(self, url):
  218.         if url[:2] == '//':
  219.             return self.open_ftp(url)
  220.         else:
  221.             return self.open_local_file(url)
  222.  
  223.     # Use local file
  224.     def open_local_file(self, url):
  225.         host, file = splithost(url)
  226.         if not host: return addinfo(open(file, 'r'), noheaders())
  227.         host, port = splitport(host)
  228.         if not port and socket.gethostbyname(host) in (
  229.               localhost(), thishost()):
  230.             file = unquote(file)
  231.             return addinfo(open(file, 'r'), noheaders())
  232.         raise IOError, ('local file error', 'not on local host')
  233.  
  234.     # Use FTP protocol
  235.     def open_ftp(self, url):
  236.         host, path = splithost(url)
  237.         if not host: raise IOError, ('ftp error', 'no host given')
  238.         host, port = splitport(host)
  239.         user, host = splituser(host)
  240.         if user: user, passwd = splitpasswd(user)
  241.         else: passwd = None
  242.         host = socket.gethostbyname(host)
  243.         if not port:
  244.             import ftplib
  245.             port = ftplib.FTP_PORT
  246.         path, attrs = splitattr(path)
  247.         dirs = string.splitfields(path, '/')
  248.         dirs, file = dirs[:-1], dirs[-1]
  249.         if dirs and not dirs[0]: dirs = dirs[1:]
  250.         key = (user, host, port, string.joinfields(dirs, '/'))
  251.         try:
  252.             if not self.ftpcache.has_key(key):
  253.                 self.ftpcache[key] = \
  254.                            ftpwrapper(user, passwd,
  255.                                   host, port, dirs)
  256.             if not file: type = 'D'
  257.             else: type = 'I'
  258.             for attr in attrs:
  259.                 attr, value = splitvalue(attr)
  260.                 if string.lower(attr) == 'type' and \
  261.                    value in ('a', 'A', 'i', 'I', 'd', 'D'):
  262.                     type = string.upper(value)
  263.             return addinfo(self.ftpcache[key].retrfile(file, type),
  264.                   noheaders())
  265.         except ftperrors(), msg:
  266.             raise IOError, ('ftp error', msg)
  267.  
  268.  
  269. # Derived class with handlers for errors we can handle (perhaps)
  270. class FancyURLopener(URLopener):
  271.  
  272.     def __init__(self, *args):
  273.         apply(URLopener.__init__, (self,) + args)
  274.         self.auth_cache = {}
  275.  
  276.     # Default error handling -- don't raise an exception
  277.     def http_error_default(self, url, fp, errcode, errmsg, headers):
  278.         return addinfo(fp, headers)
  279.  
  280.     # Error 302 -- relocated
  281.     def http_error_302(self, url, fp, errcode, errmsg, headers):
  282.         # XXX The server can force infinite recursion here!
  283.         if headers.has_key('location'):
  284.             newurl = headers['location']
  285.         elif headers.has_key('uri'):
  286.             newurl = headers['uri']
  287.         else:
  288.             return
  289.         void = fp.read()
  290.         fp.close()
  291.         return self.open(newurl)
  292.  
  293.     # Error 401 -- authentication required
  294.     # See this URL for a description of the basic authentication scheme:
  295.     # http://www.ics.uci.edu/pub/ietf/http/draft-ietf-http-v10-spec-00.txt
  296.     def http_error_401(self, url, fp, errcode, errmsg, headers):
  297.         if headers.has_key('www-authenticate'):
  298.             stuff = headers['www-authenticate']
  299.             p = regex.compile(
  300.                 '[ \t]*\([^ \t]+\)[ \t]+realm="\([^"]*\)"')
  301.             if p.match(stuff) >= 0:
  302.                 scheme, realm = p.group(1, 2)
  303.                 if string.lower(scheme) == 'basic':
  304.                     return self.retry_http_basic_auth(
  305.                         url, realm)
  306.  
  307.     def retry_http_basic_auth(self, url, realm):
  308.         host, selector = splithost(url)
  309.         i = string.find(host, '@') + 1
  310.         host = host[i:]
  311.         user, passwd = self.get_user_passwd(host, realm, i)
  312.         if not (user or passwd): return None
  313.         host = user + ':' + passwd + '@' + host
  314.         newurl = '//' + host + selector
  315.         return self.open_http(newurl)
  316.  
  317.     def get_user_passwd(self, host, realm, clear_cache = 0):
  318.         key = realm + '@' + string.lower(host)
  319.         if self.auth_cache.has_key(key):
  320.             if clear_cache:
  321.                 del self.auth_cache[key]
  322.             else:
  323.                 return self.auth_cache[key]
  324.         user, passwd = self.prompt_user_passwd(host, realm)
  325.         if user or passwd: self.auth_cache[key] = (user, passwd)
  326.         return user, passwd
  327.  
  328.     def prompt_user_passwd(self, host, realm):
  329.         # Override this in a GUI environment!
  330.         try:
  331.             user = raw_input("Enter username for %s at %s: " %
  332.                      (realm, host))
  333.             self.echo_off()
  334.             try:
  335.                 passwd = raw_input(
  336.                   "Enter password for %s in %s at %s: " %
  337.                   (user, realm, host))
  338.             finally:
  339.                 self.echo_on()
  340.             return user, passwd
  341.         except KeyboardInterrupt:
  342.             return None, None
  343.  
  344.     def echo_off(self):
  345.         import os
  346.         os.system("stty -echo")
  347.  
  348.     def echo_on(self):
  349.         import os
  350.         print
  351.         os.system("stty echo")
  352.  
  353.  
  354. # Utility functions
  355.  
  356. # Return the IP address of the magic hostname 'localhost'
  357. _localhost = None
  358. def localhost():
  359.     global _localhost
  360.     if not _localhost:
  361.         _localhost = socket.gethostbyname('localhost')
  362.     return _localhost
  363.  
  364. # Return the IP address of the current host
  365. _thishost = None
  366. def thishost():
  367.     global _thishost
  368.     if not _thishost:
  369.         _thishost = socket.gethostbyname(socket.gethostname())
  370.     return _thishost
  371.  
  372. # Return the set of errors raised by the FTP class
  373. _ftperrors = None
  374. def ftperrors():
  375.     global _ftperrors
  376.     if not _ftperrors:
  377.         import ftplib
  378.         _ftperrors = (ftplib.error_reply,
  379.                   ftplib.error_temp,
  380.                   ftplib.error_perm,
  381.                   ftplib.error_proto)
  382.     return _ftperrors
  383.  
  384. # Return an empty mimetools.Message object
  385. _noheaders = None
  386. def noheaders():
  387.     global _noheaders
  388.     if not _noheaders:
  389.         import mimetools
  390.         import StringIO
  391.         _noheaders = mimetools.Message(StringIO.StringIO(), 0)
  392.         _noheaders.fp.close()    # Recycle file descriptor
  393.     return _noheaders
  394.  
  395.  
  396. # Utility classes
  397.  
  398. # Class used by open_ftp() for cache of open FTP connections
  399. class ftpwrapper:
  400.     def __init__(self, user, passwd, host, port, dirs):
  401.         self.user = unquote(user or '')
  402.         self.passwd = unquote(passwd or '')
  403.         self.host = host
  404.         self.port = port
  405.         self.dirs = []
  406.         for dir in dirs:
  407.             self.dirs.append(unquote(dir))
  408.         self.init()
  409.     def init(self):
  410.         import ftplib
  411.         self.ftp = ftplib.FTP()
  412.         self.ftp.connect(self.host, self.port)
  413.         self.ftp.login(self.user, self.passwd)
  414.         for dir in self.dirs:
  415.             self.ftp.cwd(dir)
  416.     def retrfile(self, file, type):
  417.         import ftplib
  418.         if type in ('d', 'D'): cmd = 'TYPE A'; isdir = 1
  419.         else: cmd = 'TYPE ' + type; isdir = 0
  420.         try:
  421.             self.ftp.voidcmd(cmd)
  422.         except ftplib.all_errors:
  423.             self.init()
  424.             self.ftp.voidcmd(cmd)
  425.         conn = None
  426.         if file and not isdir:
  427.             try:
  428.                 cmd = 'RETR ' + file
  429.                 conn = self.ftp.transfercmd(cmd)
  430.             except ftplib.error_perm, reason:
  431.                 if reason[:3] != '550':
  432.                     raise IOError, ('ftp error', reason)
  433.         if not conn:
  434.             # Try a directory listing
  435.             if file: cmd = 'LIST ' + file
  436.             else: cmd = 'LIST'
  437.             conn = self.ftp.transfercmd(cmd)
  438.         return addclosehook(conn.makefile('r'), self.ftp.voidresp)
  439.  
  440. # Base class for addinfo and addclosehook
  441. class addbase:
  442.     def __init__(self, fp):
  443.         self.fp = fp
  444.         self.read = self.fp.read
  445.         self.readline = self.fp.readline
  446.         self.readlines = self.fp.readlines
  447.         self.fileno = self.fp.fileno
  448.     def __repr__(self):
  449.         return '<%s at %s whose fp = %s>' % (
  450.               self.__class__.__name__, `id(self)`, `self.fp`)
  451.     def close(self):
  452.         self.read = None
  453.         self.readline = None
  454.         self.readlines = None
  455.         self.fileno = None
  456.         if self.fp: self.fp.close()
  457.         self.fp = None
  458.  
  459. # Class to add a close hook to an open file
  460. class addclosehook(addbase):
  461.     def __init__(self, fp, closehook, *hookargs):
  462.         addbase.__init__(self, fp)
  463.         self.closehook = closehook
  464.         self.hookargs = hookargs
  465.     def close(self):
  466.         if self.closehook:
  467.             apply(self.closehook, self.hookargs)
  468.             self.closehook = None
  469.             self.hookargs = None
  470.         addbase.close(self)
  471.  
  472. # class to add an info() method to an open file
  473. class addinfo(addbase):
  474.     def __init__(self, fp, headers):
  475.         addbase.__init__(self, fp)
  476.         self.headers = headers
  477.     def info(self):
  478.         return self.headers
  479.  
  480.  
  481. # Utility to combine a URL with a base URL to form a new URL
  482.  
  483. def basejoin(base, url):
  484.     type, path = splittype(url)
  485.     host, path = splithost(path)
  486.     if type and host: return url
  487.     basetype, basepath = splittype(base)
  488.     basehost, basepath = splithost(basepath)
  489.     basepath, basetag = splittag(basepath)
  490.     basepath, basequery = splitquery(basepath)
  491.     if not type: type = basetype or 'file'
  492.     if path[:1] != '/':
  493.         i = string.rfind(basepath, '/')
  494.         if i < 0: basepath = '/'
  495.         else: basepath = basepath[:i+1]
  496.         path = basepath + path
  497.     if not host: host = basehost
  498.     if host: return type + '://' + host + path
  499.     else: return type + ':' + path
  500.  
  501.  
  502. # Utilities to parse URLs (most of these return None for missing parts):
  503. # unwrap('<URL:type//host/path>') --> 'type//host/path'
  504. # splittype('type:opaquestring') --> 'type', 'opaquestring'
  505. # splithost('//host[:port]/path') --> 'host[:port]', '/path'
  506. # splituser('user[:passwd]@host[:port]') --> 'user[:passwd]', 'host[:port]'
  507. # splitpasswd('user:passwd') -> 'user', 'passwd'
  508. # splitport('host:port') --> 'host', 'port'
  509. # splitquery('/path?query') --> '/path', 'query'
  510. # splittag('/path#tag') --> '/path', 'tag'
  511. # splitattr('/path;attr1=value1;attr2=value2;...') ->
  512. #   '/path', ['attr1=value1', 'attr2=value2', ...]
  513. # splitvalue('attr=value') --> 'attr', 'value'
  514. # splitgophertype('/Xselector') --> 'X', 'selector'
  515. # unquote('abc%20def') -> 'abc def'
  516. # quote('abc def') -> 'abc%20def')
  517.  
  518. def unwrap(url):
  519.     url = string.strip(url)
  520.     if url[:1] == '<' and url[-1:] == '>':
  521.         url = string.strip(url[1:-1])
  522.     if url[:4] == 'URL:': url = string.strip(url[4:])
  523.     return url
  524.  
  525. _typeprog = regex.compile('^\([^/:]+\):\(.*\)$')
  526. def splittype(url):
  527.     if _typeprog.match(url) >= 0: return _typeprog.group(1, 2)
  528.     return None, url
  529.  
  530. _hostprog = regex.compile('^//\([^/]+\)\(.*\)$')
  531. def splithost(url):
  532.     if _hostprog.match(url) >= 0: return _hostprog.group(1, 2)
  533.     return None, url
  534.  
  535. _userprog = regex.compile('^\([^@]*\)@\(.*\)$')
  536. def splituser(host):
  537.     if _userprog.match(host) >= 0: return _userprog.group(1, 2)
  538.     return None, host
  539.  
  540. _passwdprog = regex.compile('^\([^:]*\):\(.*\)$')
  541. def splitpasswd(user):
  542.     if _passwdprog.match(user) >= 0: return _passwdprog.group(1, 2)
  543.     return user, None
  544.  
  545. _portprog = regex.compile('^\(.*\):\([0-9]+\)$')
  546. def splitport(host):
  547.     if _portprog.match(host) >= 0: return _portprog.group(1, 2)
  548.     return host, None
  549.  
  550. _queryprog = regex.compile('^\(.*\)\?\([^?]*\)$')
  551. def splitquery(url):
  552.     if _queryprog.match(url) >= 0: return _queryprog.group(1, 2)
  553.     return url, None
  554.  
  555. _tagprog = regex.compile('^\(.*\)#\([^#]*\)$')
  556. def splittag(url):
  557.     if _tagprog.match(url) >= 0: return _tagprog.group(1, 2)
  558.     return url, None
  559.  
  560. def splitattr(url):
  561.     words = string.splitfields(url, ';')
  562.     return words[0], words[1:]
  563.  
  564. _valueprog = regex.compile('^\([^=]*\)=\(.*\)$')
  565. def splitvalue(attr):
  566.     if _valueprog.match(attr) >= 0: return _valueprog.group(1, 2)
  567.     return attr, None
  568.  
  569. def splitgophertype(selector):
  570.     if selector[:1] == '/' and selector[1:2]:
  571.         return selector[1], selector[2:]
  572.     return None, selector
  573.  
  574. _quoteprog = regex.compile('%[0-9a-fA-F][0-9a-fA-F]')
  575. def unquote(s):
  576.     i = 0
  577.     n = len(s)
  578.     res = ''
  579.     while 0 <= i < n:
  580.         j = _quoteprog.search(s, i)
  581.         if j < 0:
  582.             res = res + s[i:]
  583.             break
  584.         res = res + (s[i:j] + chr(eval('0x' + s[j+1:j+3])))
  585.         i = j+3
  586.     return res
  587.  
  588. always_safe = string.letters + string.digits + '_,.-'
  589. def quote(s, safe = '/'):
  590.     safe = always_safe + safe
  591.     res = ''
  592.     for c in s:
  593.         if c in safe:
  594.             res = res + c
  595.         else:
  596.             res = res + '%%%02x' % ord(c)
  597.     return res
  598.  
  599. # Test and time quote() and unquote()
  600. def test1():
  601.     import time
  602.     s = ''
  603.     for i in range(256): s = s + chr(i)
  604.     s = s*4
  605.     t0 = time.time()
  606.     qs = quote(s)
  607.     uqs = unquote(qs)
  608.     t1 = time.time()
  609.     if uqs != s:
  610.         print 'Wrong!'
  611.     print `s`
  612.     print `qs`
  613.     print `uqs`
  614.     print round(t1 - t0, 3), 'sec'
  615.  
  616.  
  617. # Test program
  618. def test():
  619.     import sys
  620.     import regsub
  621.     args = sys.argv[1:]
  622.     if not args:
  623.         args = [
  624.             '/etc/passwd',
  625.             'file:/etc/passwd',
  626.             'file://localhost/etc/passwd',
  627.             'ftp://ftp.cwi.nl/etc/passwd',
  628.             'gopher://gopher.cwi.nl/11/',
  629.             'http://www.cwi.nl/index.html',
  630.             ]
  631.     try:
  632.         for url in args:
  633.             print '-'*10, url, '-'*10
  634.             fn, h = urlretrieve(url)
  635.             print fn, h
  636.             if h:
  637.                 print '======'
  638.                 for k in h.keys(): print k + ':', h[k]
  639.                 print '======'
  640.             fp = open(fn, 'r')
  641.             data = fp.read()
  642.             del fp
  643.             print regsub.gsub('\r', '', data)
  644.             fn, h = None, None
  645.         print '-'*40
  646.     finally:
  647.         urlcleanup()
  648.  
  649. # Run test program when run as a script
  650. if __name__ == '__main__':
  651. ##    test1()
  652.     test()
  653.